package cn.sciento.transfer.app.service.impl;

import cn.sciento.core.exception.CommonException;
import cn.sciento.transfer.app.service.AbstractDataInitService;
import cn.sciento.transfer.domain.entity.ConsDbConfig;
import cn.sciento.transfer.domain.entity.ProducerConfig;
import cn.sciento.transfer.infra.datasource.sql.SqlBuilder;
import cn.sciento.transfer.infra.datasource.sql.SqlUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

@Service
public class DataInitDbServiceImpl extends AbstractDataInitService {
    private static final Logger LOGGER = LoggerFactory.getLogger(DataInitDbServiceImpl.class);

    @Override
    protected void preInitRunningStatus(AbstractDataInitService.DataInitContext dataInitContext) {
        ConsDbConfig consDbConfig = dataInitContext.getConsDbConfig();
        consDbConfig.setProcessStatus("R");
        consDbConfig.setProcessTime(new Date());
        consDbConfig.setProcessMsg("# 开始于 " + new Date() + "\n");
        ConsDbConfig consDbConfigDB = this.consDbConfigRepository.selectByPrimaryKey(dataInitContext.getConsDbConfig().getConsDbConfigId());
        consDbConfigDB.setProcessStatus(consDbConfig.getProcessStatus());
        consDbConfigDB.setProcessTime(consDbConfig.getProcessTime());
        consDbConfigDB.setProcessMsg(consDbConfig.getProcessMsg());
        this.consDbConfigRepository.updateOptional(consDbConfigDB, "processStatus", "processTime", "processMsg");
    }

    @Override
    protected void preInitTable(AbstractDataInitService.DataInitContext dataInitContext) {
        AbstractDataInitService.TableInitContext tempTableInitContext = AbstractDataInitService.TableInitContext.buildConsumerDBTempTable(dataInitContext);
        tempTableInitContext.buildQualifiedTableName();
        tempTableInitContext.doInit(this.transferDataSourceTemplate, (connection, tableInitContext) -> {
            SqlUtils.executeWithInOnceStatement(connection, (statement) -> statement.execute(SqlBuilder.buildDelete(new ArrayList(), tableInitContext.getInitTableName())));
        });
        dataInitContext.getConsDbConfig().addProcessMsg(String.format("# 消费DB清除主表对应的临时表[%s]数据",
                tempTableInitContext
                        .getInitQualifiedTableName()));
        AbstractDataInitService.TableInitContext tempTlTableInitContext = AbstractDataInitService.TableInitContext.buildConsumerDBTempTlTable(dataInitContext);
        if (tempTlTableInitContext != null) {
            tempTlTableInitContext.buildQualifiedTableName();
            tempTlTableInitContext.doInit(this.transferDataSourceTemplate, (connection, tableInitContext) -> {
                SqlUtils.executeWithInOnceStatement(connection, (stmt) -> stmt.execute(SqlBuilder.buildDelete(new ArrayList(), tableInitContext.getInitTableName())));
            });
            dataInitContext.getConsDbConfig().addProcessMsg(String.format("# 消费DB清除多语言表对应的临时表[%s]数据", tempTlTableInitContext
                    .getInitQualifiedTableName()));
        }
    }

    @Override
    protected void preInitSuspendConsumerGroup(AbstractDataInitService.DataInitContext dataInitContext) {
        this.mqConsumerService.suspend(dataInitContext.getConsDbConfig(), null);
    }

    @Override
    protected List<AbstractDataInitService.DataTransferContext> doInitializeBuildDataTransferContext(AbstractDataInitService.DataInitContext dataInitContext) {
        List<AbstractDataInitService.DataTransferContext> dataTransferContextList = new ArrayList<>(2);
        AbstractDataInitService.TableInitContext sourceTableInitContext = AbstractDataInitService.TableInitContext.buildProducerDBTable(dataInitContext);
        AbstractDataInitService.TableInitContext targetTableInitContext = AbstractDataInitService.TableInitContext.buildConsumerDBTempTable(dataInitContext);
        AbstractDataInitService.DataTransferContext masterDataTransferContext = internalBuildDataTransferContext(dataInitContext, sourceTableInitContext, targetTableInitContext,

                Collections.emptyList(), null, new Object());
        if (masterDataTransferContext != null) {
            dataTransferContextList.add(masterDataTransferContext);
            dataInitContext.getConsDbConfig().addProcessMsg(String.format("# 生产DB主表[%s]数据传输至消费DB主表临时表[%s]", sourceTableInitContext
                    .getInitQualifiedTableName(), targetTableInitContext
                    .getInitQualifiedTableName()));
        }
        AbstractDataInitService.TableInitContext sourceTlTableInitContext = AbstractDataInitService.TableInitContext.buildProducerDBTlTable(dataInitContext);
        AbstractDataInitService.TableInitContext targetTlTableInitContext = AbstractDataInitService.TableInitContext.buildConsumerDBTempTlTable(dataInitContext);
        AbstractDataInitService.DataTransferContext tlDataTransferContext = internalBuildDataTransferContext(dataInitContext, sourceTlTableInitContext, targetTlTableInitContext,

                Collections.emptyList(), null, new Object());
        if (tlDataTransferContext != null) {
            dataTransferContextList.add(tlDataTransferContext);
            dataInitContext.getConsDbConfig().addProcessMsg(String.format("# 生产DB多语言表[%s]数据传输至消费DB多语言临时表[%s]", sourceTlTableInitContext
                    .getInitQualifiedTableName(), targetTlTableInitContext
                    .getInitQualifiedTableName()));
        }
        return dataTransferContextList;
    }

    @Override
    protected void postInitRefreshStatus(AbstractDataInitService.DataInitContext dataInitContext) {
        ConsDbConfig consDbConfig = this.consDbConfigRepository.selectByPrimaryKey(dataInitContext.getConsDbConfig().getConsDbConfigId());
        consDbConfig.setProcessTime(new Date());
        consDbConfig.setProcessStatus("S");
        consDbConfig.setProcessMsg(dataInitContext.getConsDbConfig().addProcessMsg("# 完成于 " + new Date()).getProcessMsg());
        Long offset = internalBuildOffset(dataInitContext, null);
        if (offset != null) {
            consDbConfig.setConsumerOffset(offset + 1L);
            consDbConfig.setOffsetChangeFlag(Boolean.TRUE);
            dataInitContext.getConsDbConfig().setConsumerOffset(offset + 1L);
            dataInitContext.getConsDbConfig().setOffsetChangeFlag(Boolean.TRUE);
        }
        this.consDbConfigRepository.updateOptional(consDbConfig, "processTime", "processStatus", "processMsg", "consumerOffset");
    }

    @Override
    protected void postInitRefreshTargetTableCustomize(Connection connection, AbstractDataInitService.DataInitContext dataInitContext) throws SQLException {
        ProducerConfig producerConfig = dataInitContext.getProducerConfig();
        ConsDbConfig consDbConfig = dataInitContext.getConsDbConfig();
        String backupTableName = internalBackupThenExchangeTable(connection, consDbConfig
                .getConsumerTable(), producerConfig
                .getInitTempTable(), "_bak");
        producerConfig.setBackupTableName(backupTableName);
        dataInitContext.getConsDbConfig().addProcessMsg(String.format("# 消费DB更名主表[%s]至备份表[%s], 然后将临时表[%s]更名为主表", consDbConfig
                .getConsumerTable(), backupTableName, producerConfig

                .getInitTempTable()));
        if (StringUtils.isNotEmpty(dataInitContext.getProducerConfig().getTlTableName())) {
            String backupTlTableName = internalBackupThenExchangeTable(connection, consDbConfig

                    .getConsumerTlTable(), producerConfig
                    .getInitTempTlTable(), "_bak_tl");
            producerConfig.setBackupTlTableName(backupTlTableName);
            dataInitContext.getConsDbConfig().addProcessMsg(String.format("# 消费DB更名多语言表[%s]至备份表[%s], 然后将临时表[%s]更名为多语言表", consDbConfig
                    .getConsumerTlTable(), backupTlTableName, producerConfig

                    .getInitTempTlTable()));
            SqlUtils.executeWithInOnceStatementThenClose(connection.createStatement(), stmt -> {
                if (StringUtils.isNotEmpty(backupTlTableName)) {
                    String dropBackupTable = SqlBuilder.builderDropTable(backupTlTableName);
                    stmt.execute(dropBackupTable);
                }
                String dropTempTable = SqlBuilder.builderDropTable(producerConfig.getInitTempTlTable());
                stmt.execute(dropTempTable);
                return null;
            });
        }
        SqlUtils.executeWithInOnceStatementThenClose(connection.createStatement(), stmt -> {
            if (StringUtils.isNotEmpty(backupTableName)) {
                String dropBackupTable = SqlBuilder.builderDropTable(backupTableName);
                stmt.execute(dropBackupTable);
            }
            String dropTempTable = SqlBuilder.builderDropTable(producerConfig.getInitTempTable());
            stmt.execute(dropTempTable);
            return null;
        });
    }

    @Override
    protected void handleExceptionRestoreData(Connection connection, AbstractDataInitService.DataInitContext dataInitContext) {
    }

    @Override
    protected void handleExceptionRestoreTable(Connection connection, AbstractDataInitService.DataInitContext dataInitContext) {
        ProducerConfig producerConfig = dataInitContext.getProducerConfig();
        ConsDbConfig consDbConfig = dataInitContext.getConsDbConfig();
        String productionTableName = consDbConfig.getConsumerTable();
        String backupTableName = producerConfig.getBackupTableName();
        if (StringUtils.isNotEmpty(backupTableName)) {
            try {
                internalBackupThenExchangeTable(connection, productionTableName, backupTableName, "_restore_tl");
                dataInitContext.getConsDbConfig().addProcessMsg(String.format("# 消费DB从备份表%s恢复至主表%s", backupTableName, productionTableName));
            } catch (Exception ex) {
                LOGGER.info("restore table from {} to {} failed with: ", backupTableName, productionTableName);
                throw new CommonException(ex);
            }
        }
        String productionTlTableName = consDbConfig.getConsumerTlTable();
        String backupTlTableName = producerConfig.getBackupTlTableName();
        if (StringUtils.isNotEmpty(backupTlTableName)) {
            try {
                internalBackupThenExchangeTable(connection, productionTlTableName, backupTlTableName, "_restore_tl");
                dataInitContext.getConsDbConfig().addProcessMsg(String.format("# 消费DB从备份表%s恢复至多语言表%s", backupTlTableName, productionTlTableName));
            } catch (Exception ex) {
                LOGGER.info("restore tl table from {} to {} failed with: ", backupTlTableName, productionTlTableName);
                throw new CommonException(ex);
            }
        }
    }

    @Override
    protected void handleExceptionCustomize(AbstractDataInitService.DataInitContext dataInitContext, Exception ex) {
        ConsDbConfig consDbConfigDB = this.consDbConfigRepository.selectByPrimaryKey(dataInitContext.getConsDbConfig().getConsDbConfigId());
        consDbConfigDB.setProcessStatus("E");
        consDbConfigDB.setProcessTime(new Date());
        consDbConfigDB.setProcessMsg(dataInitContext.getConsDbConfig().addProcessMsg(ExceptionUtils.getStackTrace(ex)).getProcessMsg());
        this.consDbConfigRepository.updateOptional(consDbConfigDB, "processStatus", "processTime", "processMsg");
    }
}
